/**
   ******************************************************************************
   * @file    main.c 
   * @author  18408107475@163.com
   * @version v1.0
   * @date    2025/03/16
   * @brief   实现通过库函数和寄存器来利用MCU的KEY0\KEY1按键控制LED0和LED1亮灭
   *                                        (PE4\PE5)       (PB5、PE5)
   ******************************************************************************
**/

#include "stm32f10x.h"// 包含STM32F10x系列微控制器的头文件，该文件中定义了所有的寄存器映射和外设库函数

/**
    ******************************************************************************
    * Function name  : LED_STD_Config
    * Description    : 利用库函数对GPIO端口C的PB5、PE5口进行初始化
    * Parameter      : None
    * Return         : void
    * note           : 配置顺序:
    *							1.定义结构体变量	2.打开对应外设的时钟
    *							3.配置端口引脚模式  4.初始化
    * Modify history : None
    * version        : v1.0
    ******************************************************************************
**/
void LED_STD_Config(void)
{
    /************************************标准库配置*************************************/
    //1.定义GPIO外设的结构体变量
    // GPIO_InitTypeDef是在stm32f10x_gpio.h头文件中定义的结构体类型，用于配置GPIO引脚的相关参数
    GPIO_InitTypeDef GPIO_InitStructure;

    //2.默认复位后时钟关闭，需打开对应时钟为寄存器（触发器）提供信号；外设GPIO --> 总线APB2
    // RCC_APB2PeriphClockCmd函数用于使能或禁用APB2总线上的外设时钟
    // RCC_APB2Periph_GPIOx表示GPIOx端口的时钟，ENABLE表示使能该时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOE, ENABLE);

    //3.配置GPIO端口B的引脚5；推挽输出模式、50MHz速度
    // GPIO_Pin_5表示选择GPIOB端口的第5个引脚
    GPIO_InitStructure.GPIO_Pin   = GPIO_Pin_5;
    // GPIO_Speed_50MHz表示该引脚的输出速度为50MHz
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    // GPIO_Mode_Out_PP表示该引脚配置为通用推挽输出模式
    GPIO_InitStructure.GPIO_Mode  = GPIO_Mode_Out_PP;

    GPIO_Init(GPIOB, &GPIO_InitStructure);

    //3.配置GPIO端口E的引脚5；推挽输出模式、50MHz速度
    // GPIO_Pin_5表示选择GPIOE端口的第5个引脚
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;

    //4.将配置好的数据传入GPIO初始化函数中
    // GPIO_Init函数用于根据GPIO_InitStructure结构体中的参数初始化指定的GPIO端口
    // GPIOC表示要初始化的端口为GPIOE，&GPIO_InitStructure表示结构体变量的地址
    GPIO_Init(GPIOE, &GPIO_InitStructure);

    //5.默认关闭LED
    GPIO_SetBits(GPIOB,GPIO_Pin_5);
    GPIO_SetBits(GPIOE,GPIO_Pin_5);

}


/**
    ******************************************************************************
    * Function name  : KEY_STD_Config
    * Description    : 利用库函数对GPIO端口E的PE4、PE3口进行初始化，将其配置为上拉输入模式，
    *                  常用于连接按键等输入设备，按键按下时引脚电平被拉低，释放时为高电平。
    * Parameter      : None
    * Return         : void
    * note           : 配置顺序:
    *                  1. 定义结构体变量
    *                  2. 打开对应外设的时钟
    *                  3. 配置端口引脚模式
    *                  4. 初始化
    * Modify history : None
    * version        : v1.0
    ******************************************************************************
**/
void KEY_STD_Config(void)
{
    /************************************标准库配置*************************************/
    // 1. 定义GPIO外设的结构体变量
    // GPIO_InitTypeDef 是在 stm32f10x_gpio.h 头文件中定义的结构体类型，
    // 用于存储和配置GPIO引脚的相关参数，如引脚号、工作模式、输出速度等。
    // 后续通过对该结构体成员赋值，将配置信息传递给 GPIO_Init 函数。
    GPIO_InitTypeDef GPIO_InitStructure;

    // 2. 打开对应外设的时钟
    // 在STM32中，默认复位后各个外设的时钟是关闭的，需要手动使能。
    // GPIO 外设挂载在 APB2 总线上，因此使用 RCC_APB2PeriphClockCmd 函数来使能对应 GPIO 端口的时钟。
    // RCC_APB2Periph_GPIOE 是一个宏定义，表示 GPIOE 端口的时钟使能位。
    // ENABLE 是一个枚举值，将其作为参数传递给 RCC_APB2PeriphClockCmd 函数，使能 GPIOE 端口的时钟，
    // 这样后续才能对 GPIOE 端口的寄存器进行正常的读写操作。
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE, ENABLE);

    // 3. 配置端口引脚模式
    // 3.1 选择要配置的引脚
    // GPIO_InitStructure.GPIO_Pin 成员用于指定要配置的 GPIO 引脚。
    // 使用按位或运算符（|）将 GPIO_Pin_4 和 GPIO_Pin_3 组合起来，表示同时选择 GPIOE 端口的第 4 个引脚和第 3 个引脚。
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4 | GPIO_Pin_3;

    // 3.2 设置引脚的工作模式
    // GPIO_InitStructure.GPIO_Mode 成员用于指定 GPIO 引脚的工作模式。
    // 这里选择 GPIO_Mode_IPU，即上拉输入模式。在该模式下，芯片内部会连接一个上拉电阻到电源，
    // 当没有外部信号输入时，引脚电平被拉高到电源电平（通常为 3.3V）。
    // 对于连接按键的场景，按键一端接地，另一端连接到配置为上拉输入的引脚，
    // 按键未按下时引脚为高电平，按下按键时引脚电平被拉低，方便通过检测引脚电平变化来判断按键状态。
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;

    // 注意：对于输入模式，输出速度参数无效，无需设置。
    // 因为输入模式下引脚不涉及输出信号，所以输出速度参数不会对输入功能产生影响，
    // 这里省略输出速度的设置，避免不必要的操作。

    // 4. 将配置好的数据传入 GPIO 初始化函数中
    // GPIO_Init 函数是 STM32 标准库提供的用于初始化 GPIO 端口的函数。
    // 第一个参数 GPIOE 表示要初始化的端口为 GPIOE。
    // 第二个参数 &GPIO_InitStructure 是结构体变量的地址，函数通过该地址获取之前配置好的参数，
    // 并将这些参数写入到 GPIOE 端口的相应寄存器中，完成 GPIO 引脚的初始化配置。
    GPIO_Init(GPIOE, &GPIO_InitStructure);
}


/**
    ******************************************************************************
    * Function name  : LED_ST_Reg_config
    * Description    : 利用ST公司定义的寄存器对GPIO端口B和E的Pin5口进行初始化
    * Parameter      : None
    * Return         : void
    * note           : 配置顺序:
    *                   1. 打开对应外设的时钟
    *                   2. 配置端口引脚模式
    * Modify history : None
    * version        : v1.0
    ******************************************************************************
**/
void LED_ST_Reg_config(void)
{
    /************************************ST提供的寄存器配置*************************************/
    // 1. 打开对应外设的时钟
    // RCC 是指向复位和时钟控制 (RCC) 寄存器组的结构体指针
    // APB2ENR 是 RCC 寄存器组中的 APB2 外设时钟使能寄存器，用于控制 APB2 总线上外设的时钟使能
    // RCC_APB2ENR_IOPBEN 是一个宏定义，表示 APB2ENR 寄存器中使能 GPIOB 时钟的位
    // 通过按位或操作将该位置 1，开启 GPIOB 端口的时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN;
    // RCC_APB2ENR_IOPEEN 是一个宏定义，表示 APB2ENR 寄存器中使能 GPIOE 时钟的位
    // 通过按位或操作将该位置 1，开启 GPIOE 端口的时钟
    RCC->APB2ENR |= RCC_APB2ENR_IOPEEN;

    // 2. 配置端口引脚模式
    // 对于 GPIO 端口，每个引脚的配置由 4 位控制，分别是 CNF[1:0]（配置模式位）和 MODE[1:0]（输出速度位）
    // 每个 GPIO 端口有两个 32 位的配置寄存器 CRL（用于配置引脚 0 - 7）和 CRH（用于配置引脚 8 - 15）
    // 这里要配置的是 GPIOB 和 GPIOE 的 Pin5，它们使用 CRL 寄存器

    // 配置输出速度为 50MHz
    // GPIO_CRL_MODE5_0 和 GPIO_CRL_MODE5_1 是在 stm32f10x_gpio.h 头文件中定义的宏
    // 分别对应 GPIOC_CRL 寄存器中 PB5 引脚的输出速度控制位的第 0 位和第 1 位
    // 将这两位都置 1 表示输出模式，最大速度 50MHz
    GPIOB->CRL |= (GPIO_CRL_MODE5_0 | GPIO_CRL_MODE5_1);
    // 同样地，对 GPIOE 的 Pin5 进行输出速度配置
    GPIOE->CRL |= (GPIO_CRL_MODE5_0 | GPIO_CRL_MODE5_1);

    // 配置为通用推挽输出模式
    // GPIO_CRL_CNF5 是在 stm32f10x_gpio.h 头文件中定义的宏
    // 表示 GPIOB_CRL 寄存器中 PB5 引脚的配置模式控制位
    // 通过按位与和取反操作将这些位清零，表示通用推挽输出模式
    GPIOB->CRL &= ~(GPIO_CRL_CNF5);
    // 同样地，对 GPIOE 的 Pin5 进行配置模式设置
    GPIOE->CRL &= ~(GPIO_CRL_CNF5);
}


/**
    ******************************************************************************
    * Function name  : KEY_ST_Reg_config
    * Description    : 利用寄存器操作对GPIO端口E的PE4、PE3口进行初始化，将其配置为上拉输入模式，
    *                  常用于连接按键等输入设备，按键按下时引脚电平被拉低，释放时为高电平。
    * Parameter      : None
    * Return         : void
    * note           : 配置顺序:
    *                  1. 打开对应外设的时钟
    *                  2. 配置端口引脚模式
    *                  3. 设置上拉电阻
    * Modify history : None
    * version        : v1.0
    ******************************************************************************
**/
void KEY_ST_Reg_config(void)
{
    // 1. 打开对应外设的时钟
    // RCC 是指向复位和时钟控制寄存器组的结构体指针
    // APB2ENR 是 RCC 寄存器组中的 APB2 外设时钟使能寄存器
    // RCC_APB2ENR_IOPEEN 是一个宏定义，表示 APB2ENR 寄存器中使能 GPIOE 时钟的位
    // 通过按位或操作将该位置 1，开启 GPIOE 端口的时钟，这样后续才能对 GPIOE 端口进行配置
    RCC->APB2ENR |= RCC_APB2ENR_IOPEEN;

    // 2. 配置端口引脚模式
    // GPIOE 是指向 GPIOE 端口寄存器组的结构体指针
    // CRL 是 GPIOE 端口的低 8 位配置寄存器，用于配置引脚 0 - 7
    // 每个引脚由 4 位控制，其中低 2 位为 MODE 位，用于设置引脚的输出速度或输入模式；高 2 位为 CNF 位，用于设置引脚的具体工作模式
    // PE3 对应 CRL 寄存器的第 12 - 15 位，PE4 对应 CRL 寄存器的第 16 - 19 位
    // 对于输入模式，需要将 MODE 位（低 2 位）设置为 00，表示输入模式
    // 对于上拉/下拉输入模式，需要将 CNF 位（高 2 位）设置为 10
    // 先将 PE3 和 PE4 的 MODE 位清零
    GPIOE->CRL &= ~(GPIO_CRL_MODE3 | GPIO_CRL_MODE4);
    // 再将 PE3 和 PE4 的 CNF 位设置为 10
    GPIOE->CRL |= (GPIO_CRL_CNF3_1 | GPIO_CRL_CNF4_1);

    // 3. 设置上拉电阻
    // ODR 是 GPIOE 端口的输出数据寄存器
    // 在引脚配置为上拉/下拉输入模式后，通过将 ODR 寄存器中 PE3 和 PE4 对应的位置 1，启用内部上拉电阻
    // 当引脚没有外部信号输入时，上拉电阻会将引脚电平拉高到电源电平
    GPIOE->ODR |= (GPIO_Pin_3 | GPIO_Pin_4);
}

int main()
{
    // 选择初始化方式，可以根据需要注释或取消注释下面的函数调用，选择不同的初始化方式
    LED_STD_Config();
    KEY_STD_Config();
    //LED_ST_Reg_config();
    //KEY_ST_Reg_config();
    while(1)
    {
        //处理 GPIOE_Pin_3 控制 GPIOB_Pin5 的情况
        switch (GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_3)) {
            case 0: GPIO_ResetBits(GPIOB, GPIO_Pin_5);  break;
            default:    GPIO_SetBits(GPIOB, GPIO_Pin_5);    break;
        }
        //处理 GPIOE_Pin_4 控制 GPIOE_Pin5 的情况
        switch (GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_4)) {
            case 0: GPIO_ResetBits(GPIOE, GPIO_Pin_5);  break;
            default:    GPIO_SetBits(GPIOE, GPIO_Pin_5);    break;
        }

                
        /********************************标准库操作************************************/
        /*
         * 处理 GPIOE_Pin_3 控制 GPIOB_Pin5 的情况
         * switch (GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_3)) {
         *     case 0: GPIO_ResetBits(GPIOB, GPIO_Pin_5);  break;
         *     default:    GPIO_SetBits(GPIOB, GPIO_Pin_5);    break;
         * }
         * 处理 GPIOE_Pin_4 控制 GPIOE_Pin5 的情况
         * switch (GPIO_ReadInputDataBit(GPIOE, GPIO_Pin_4)) {
         *     case 0: GPIO_ResetBits(GPIOE, GPIO_Pin_5);  break;
         *     default:    GPIO_SetBits(GPIOE, GPIO_Pin_5);    break;
         * }
         */

        /*******************************ST寄存器操作***********************************/
        /* 
         * if (!(GPIOE->IDR & GPIO_Pin_3)) {  // PE3低电平（按键按下）
         *     GPIOB->BSRR = GPIO_Pin_5 << 16;  // 清除PB5（BSRR的BRx位写1）
         * } else {                            // PE3高电平（按键释放）
         *     GPIOB->BSRR = GPIO_Pin_5;       // 置位PB5（BSRR的BSx位写1）
         * }
         * 检测PE4输入，控制PE5输出
         * if (!(GPIOE->IDR & GPIO_Pin_4)) {  // PE4低电平（按键按下）
         *     GPIOE->BSRR = GPIO_Pin_5 << 16;  // 清除PE5
         * } else {                            // PE4高电平（按键释放）
         *     GPIOE->BSRR = GPIO_Pin_5;       // 置位PE5
         * }
         */
    }

}
